package com.wyp168.socket.QQ.qqserver;

import com.wyp168.socket.QQ.qqclient.common.Message;
import com.wyp168.socket.QQ.qqclient.common.User;
import com.wyp168.socket.QQ.qqclient.common.constant.MessageType;
import com.wyp168.socket.QQ.qqserver.services.ManagerClientSocketThread;
import com.wyp168.socket.QQ.qqserver.services.ServerConnectClientThread;
import com.wyp168.socket.QQ.qqserver.services.ServerPushMessageThread;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @ClassName QQServer
 * @Title 服务端
 * @Description 监听9999 端口，等待客户端连接，并保持通讯
 * @Author wyp168
 * @Date 2022/10/30 16:01
 */
public class QQServer {

    private ServerSocket serverSocket = null;

    private static final boolean loop = true;

    //定义一个HashMap，模拟数据库db，存放离线消息
    public static ConcurrentHashMap<String, ArrayList<Message>> offLineDb = new ConcurrentHashMap<>();

    /**
     * 该map集合是为了模拟存放用户名和密码，方便登录验证
     * HashMap 没有处理线程安全， 因此在多线程情况下是不安全的
     * ConcurrentHashMap 处理了线程安全，即线程同步，因此在多线程情况下是安全的
     */
    private static final Map<String, User> validUsers = new ConcurrentHashMap<>();
    static {
        validUsers.put("admin", new User("admin", "admin"));
        validUsers.put("1001", new User("1001", "123123"));
        validUsers.put("1002", new User("1002", "123123"));
        validUsers.put("1003", new User("1003", "123123"));
        validUsers.put("至尊宝", new User("至尊宝", "123123"));
        validUsers.put("紫霞仙子", new User("紫霞仙子", "123123"));
        validUsers.put("菩提老祖", new User("菩提老祖", "123123"));
        validUsers.put("玉皇大帝", new User("玉皇大帝", "123123"));
    }

    public QQServer(int port) {
        ObjectOutputStream oos = null;
        try {
            System.out.println("服务端在" + port + "端口保持监听...");
            //设置服务器推送线程，并设置为守护线程
            ServerPushMessageThread serverPushMessageThread = new ServerPushMessageThread();
            serverPushMessageThread.setDaemon(true);
            serverPushMessageThread.start();

            serverSocket = new ServerSocket(port);
            /**
             * 服务端监听多个客户端
             */
            while (loop) {
                Socket socket = serverSocket.accept();  //如果没有客户端连接，则会阻塞
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                User user = (User) ois.readObject();
                // 验证用户名和密码是否正确
                Message message = new Message();
                if (checkUserLogin(user.getUserId(), user.getPassword())) {  //验证通过
                    message.setMessageType(MessageType.MESSAGE_LOGIN_SUCCEED);
                    oos = new ObjectOutputStream(socket.getOutputStream());
                    oos.writeObject(message);

//                    socket.shutdownOutput();
                    //创建一个线程，保存当前通信的socket
                    ServerConnectClientThread serverConnectClientThread =
                            new ServerConnectClientThread(socket, user);
                    serverConnectClientThread.start();
                    //把该线程对象 放入到服务端对应的线程集合中，进行集中管理
                    ManagerClientSocketThread.addClientSocket(user.getUserId(), serverConnectClientThread);

                    //如果之前有用户给该用户留言则，服务端将发送离线留言给登录成功的客户端
                    if (offLineDb.get(user.getUserId()) != null && offLineDb.get(user.getUserId()).size() != 0) {
                        //调用指定userId的socket，推送离线消息
                        ObjectOutputStream offLineOOS = new ObjectOutputStream(ManagerClientSocketThread.getServerConnectClientThread(user.getUserId()).getSocket().getOutputStream());
                        //遍历离线内容message集合，并且将所有离线内容都推送给指定用户(userId)
                        for (Message message1 : offLineDb.get(user.getUserId())) {
                            offLineOOS.writeObject(message1);
                        }
                        //移除相应userId对应的离线消息记录
                        offLineDb.remove(user.getUserId());
                    }

                } else {    //登录失败
                    message.setMessageType(MessageType.MESSAGE_LOGIN_FAILED);
                    oos = new ObjectOutputStream(socket.getOutputStream());
                    oos.writeObject(message);
//                    socket.shutdownOutput();
                    //登录失败，关闭socket
                    socket.close();
                }
            }
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            // 如果服务端退出了while循环， 说明服务器端不再监听
            if (serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 判断当前传入用户名和密码是否合法
     * @param userId 用户名id/ 客户端id
     * @param password 密码
     * @return 返回是否合法： true： 合法； false： 不合法
     */
    private boolean checkUserLogin(String userId, String password) {
        User user;
        if ((user = validUsers.get(userId)) == null) {  //如果根据userId查询validUsers 为空，则返回false
            return false;
        }
        return user.getPassword().equals(password);
    }

    public static void main(String[] args) {
        QQServer qqServer = new QQServer(9999);
    }
}
